package com.chrishobson.jreeglut;

import static  com.chrishobson.jreeglut.glut.*;

import java.util.HashMap;

/**
 * Java wrapper to window in glut. A Window can be made either by calling the
 * static glut.CreateWindow method or by the more OO method of newing a
 * glutWindow. When glut issues a callback it is routed by the jreeglut JNI
 * into static methods of this class, and then it is forwarded to the
 * appropriate handler for the appropriate window.
 * <p>
 * All of the static methods that glut invokes catch all Throwables
 * be they from applications own exceptions or from the JVM. A message
 * is printed in the catch block. This is done as the glut callbacks
 * do not return values and so glut cannot know of the error and
 * so there is nothing useful the JNI code could do with them so 
 * there is no sense complicating it by allowing them remain in effect
 * when the static method returns. 
 * 
 * @author clh
 * 
 */
public class glutWindow {

  public glutWindow(String a_Name) {
    m_Id = Integer.valueOf(glutCreateWindow(a_Name));
    s_Map.put(m_Id, this);
  }

  /**
   * Makes this window the current glut window. Normally you do not need to call
   * this as the Window class will do it when necessary.
   */
  public void SetWindow() {
    glutSetWindow(m_Id.intValue());
  }
  
  /**
   * Request that this window is redisplayed as soon as possible. After the
   * call this window will be the current glut window.
   */
  public void PostRedisplay()
  {
    SetWindow();
    glutPostRedisplay();
  }

  public void AttachMenu(
    glutMenu a_Menu
  , int a_Button
  )
  {
    a_Menu.SetMenu();
    SetWindow();
    glutAttachMenu(glutMenu.class, a_Button);
  }
  
  
  /**
   * Sets up so that motion callbacks for the Window are delivered to the
   * specified handler. If the handler is null then any existing call back for
   * the window is cancelled.
   * 
   * @param a_h
   */
  public void SetMotionHandler(MotionHandler a_h) {
    SetWindow();
    // Tell glut to start tracking Motion
    glut.MotionFunc(a_h == null ? null : glutWindow.class);
    m_MotionHandler = a_h;
  }

  /**
   * Sets up so that passive motion callbacks for the Window are delivered to
   * the specified handler. If the handler is null then any existing call back
   * for the window is cancelled.
   * 
   * @param a_h
   */
  public void SetPassiveMotionHandler(PassiveMotionHandler a_h) {
    SetWindow();
    // Tell glut to start tracking passive motion
    glut.PassiveMotionFunc(a_h == null ? null : glutWindow.class);
    m_PassiveMotionHandler = a_h;
  }

  /**
   * Setup so that window reshapes are delivered to the specified handler. If
   * the handler is null then any existing call back for the window is
   * cancelled.
   * 
   * @param a_h
   */
  public void SetReshapeHandler(ReshapeHandler a_h) {
    SetWindow();
    // Tell glut to sending reshape events
    glut.ReshapeFunc(a_h == null ? null : glutWindow.class);
    m_ReshapeHandler = a_h;
  }

  /**
   * Setup so that window Display events are delivered to the specified handler.
   * You must have a display handler, and you cannot set a display handler
   * to null, but you can set a new handler.
   * 
   * @param a_h
   */
  public void SetDisplayHandler(DisplayHandler a_h) {
    a_h.toString();
    
    SetWindow();
    // Tell glut to sending reshape events
    glut.DisplayFunc(glutWindow.class);
    m_DisplayHandler = a_h;
  }

  /**
   * Setup so that window Mouse events are delivered to the specified handler.
   * If the handler is null then any existing call back for the window is
   * cancelled.
   * 
   * @param a_h
   */
  public void SetMouseHandler(MouseHandler a_h) {
    SetWindow();
    // Tell glut to sending keyboard events
    glut.MouseFunc(a_h == null ? null : glutWindow.class);
    m_MouseHandler = a_h;
  }
  /**
   * Setup so that window Special Keyboard events are delivered to the specified handler.
   * If the handler is null then any existing call back for the window is
   * cancelled.
   * 
   * @param a_h
   */
  public void SetSpecialHandler(SpecialHandler a_h) {
    SetWindow();
    // Tell glut to sending keyboard events
    glut.SpecialFunc(a_h == null ? null : glutWindow.class);
    m_SpecialHandler = a_h;
  }
  /**
   * Setup so that window Keyboard events are delivered to the specified handler.
   * If the handler is null then any existing call back for the window is
   * cancelled.
   * 
   * @param a_h
   */
  public void SetKeyboardHandler(KeyboardHandler a_h) {
    SetWindow();
    // Tell glut to sending keyboard events
    glut.KeyboardFunc(a_h == null ? null : glutWindow.class);
    m_KeyboardHandler = a_h;
  }
  /**
   * Setup so that window menu events are delivered to the specified handler.
   * If the handler is null then any existing call back for the window is
   * cancelled.
   * 
   * @param a_h
   */

  public void SetMenuHandler(MenuHandler a_h) {
    m_MenuHandler = a_h;
  }

  /**
   * This is the motion callback from glut. We get given the type (Motion or
   * PassiveMotion), the WindowId that the motion is in and the X,Y.
   * 
   * @param a_Type
   * @param a_WindowId
   * @param a_X
   * @param a_Y
   */
  @SuppressWarnings("unused")
  private static void Motion(int a_Type, int a_WindowId, int a_X, int a_Y) {
    try {
      glutWindow l_Wind = MapWindow(a_WindowId);
      if (a_Type == 0) {
        l_Wind.m_MotionHandler.MotionFunc(new MotionData(l_Wind, a_X, a_Y));
      } else {
        l_Wind.m_PassiveMotionHandler.PassiveMotionFunc(new PassiveMotionData(l_Wind, a_X, a_Y));
      }
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  /**
   * This is the reshape callback from glut. We get given the the WindowId that
   * the reshape is in and the X,Y.
   * 
   * @param a_WindowId
   * @param a_X
   * @param a_Y
   */
  @SuppressWarnings("unused")
  private static void Reshape(int a_WindowId, int a_X, int a_Y) {
    try {
      glutWindow l_Wind = MapWindow(a_WindowId);

      l_Wind.m_ReshapeHandler.ReshapeFunc(new ReshapeData(l_Wind, a_X, a_Y));
    } catch (Throwable e) {
      e.printStackTrace();
    }

  }

  /**
   * This is the Display callback from glut. We get given the the WindowId that
   * the Display is in.
   * 
   * @param a_WindowId
   */
  @SuppressWarnings("unused")
  private static void Display(int a_WindowId) {
    try {
      glutWindow l_Wind = MapWindow(a_WindowId);

      l_Wind.m_DisplayHandler.DisplayFunc(new DisplayData(l_Wind));
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  /**
   * This is the Keyboard callback from glut. We get given the the WindowId that
   * the Keyboard event is in.
   * 
   * @param a_WindowId
   */
  @SuppressWarnings("unused")
  private static void Keyboard(int a_WindowId, int a_Key, int a_X, int a_Y) {
    try {
      glutWindow l_Wind = MapWindow(a_WindowId);

      l_Wind.m_KeyboardHandler.KeyboardFunc(
          new KeyboardData(l_Wind, a_Key, a_X, a_Y)) ;
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  /**
   * This is the Special callback from glut. We get given the the WindowId that
   * the Special Keyboard event is in.
   * 
   * @param a_WindowId
   */
  @SuppressWarnings("unused")
  private static void Special(int a_WindowId, int a_Key, int a_X, int a_Y) {
    try {
      glutWindow l_Wind = MapWindow(a_WindowId);

      l_Wind.m_SpecialHandler.SpecialFunc(
          new KeyboardData(l_Wind, a_Key, a_X, a_Y)) ;
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  /**
   * This is the Mouse callback from glut. We get given the the WindowId that
   * the mouse event is in.
   * 
   * @param a_WindowId
   */
  @SuppressWarnings("unused")
  private static void Mouse(int a_WindowId, int a_Button, int a_State, int a_X, int a_Y) {
    try {
      glutWindow l_Wind = MapWindow(a_WindowId);

      l_Wind.m_MouseHandler.MouseFunc(new MouseData(l_Wind, a_Button, a_State, a_X, a_Y));
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  /**
   * This is the Menu callback, glut invokes a method in the Menu class
   * which creates the MenuData object and forwards to the Window class
   * for dispatch to the menu handler for the Window in which the menu
   * was selected.
   * 
   * @param a_WindowId
   */
  static void Menu(int a_WindowId, MenuData a_Data) {
    try {
      glutWindow l_Wind = MapWindow(a_WindowId);

      l_Wind.m_MenuHandler.MenuFunc(l_Wind, a_Data);
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }

  /**
   * Get the OpenGL context of this window 
   * @return The HGLRC cast as a long
   */
  private long wglContext()
  {
    SetWindow();
    return wglGetCurrentContext();
  }
  
  /**
   * Allow "this" Window to share the display list name space
   * of the "main" Window. "this" Window must not have any display
   * lists associated with it.
   * @param main The main window whose name space we share
   * @return true if this worked
   */
  public boolean ShareLists(glutWindow main) {
    return wglShareLists(main.wglContext(), wglContext());
  }
  
  /**
   * Maps a glut Window id to a glutWindow
   * 
   * @param a_Id
   *          The glut window id
   * @return a glutWindow
   */
  static private glutWindow MapWindow(int a_Id) {
    return s_Map.get(a_Id);
  }

  private Integer m_Id;
  private MotionHandler m_MotionHandler;
  private PassiveMotionHandler m_PassiveMotionHandler;
  private ReshapeHandler m_ReshapeHandler;
  private DisplayHandler m_DisplayHandler;
  private KeyboardHandler m_KeyboardHandler;
  private SpecialHandler m_SpecialHandler;
  private MouseHandler m_MouseHandler;
  private MenuHandler m_MenuHandler;

  static private HashMap<Integer, glutWindow> s_Map = new HashMap<Integer, glutWindow>();
}
